import numpy as np


def get_anneal(name: str, perid, min, max, ):
    name = name.lower()
    if name == 'cosine':
        return CosineAnneal(perid, min, max, True)
    elif name == 'cyclic':
        return Cyclical(perid, min, max, True)
    elif name == 'constant':
        return Constant(perid, min, max, True)
    elif name == 'monotonic':
        return Monotonic(perid, min, max, False)
    else:
        raise NotImplementedError()


class Anneal:
    def __init__(self, perid, min, max, cyclic=True):
        self.perid = perid
        self.min = min
        self.max = max
        self.step = 1
        self.cyclic = cyclic

    def p(self):
        """
        :return: process 0-1
        """
        self.step += 1
        if self.step > self.perid:
            if self.cyclic:
                self.step -= self.perid
            else:
                return 1.0

        return (self.step) / self.perid

    def next(self):
        raise NotImplementedError()


class CosineAnneal(Anneal):
    def next(self):
        p = self.p()
        if p < 0.5:
            return self.max
        else:
            return np.sin(p * np.pi) * (self.max - self.min) + self.min


class Cyclical(Anneal):
    def next(self):
        p = self.p()
        d = (self.max - self.min)
        if p < 0.5:
            return self.min + d * 2 * p
        else:
            return self.max


class Constant(Anneal):
    def next(self):
        return self.max


class Monotonic(Anneal):
    def next(self):
        p = self.p()
        d = (self.max - self.min)
        return self.min + d * p


if __name__ == '__main__':
    a = get_anneal('cyclic', 100, 0, 1)
    log = []
    for i in range(200):
        log.append(a.next())
